package org.xx.armory.commons;

import org.slf4j.LoggerFactory;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.util.Base64;

import static org.apache.commons.lang3.StringUtils.isEmpty;
import static org.xx.armory.commons.Validators.rejectIfNull;

/**
 * 用于签名和验证签名的工具类。
 */
public final class SignatureUtils {
    private static final String DEFAULT_SIGN_ALGORITHM = "SHA1withRSA";
    private static final int DEFAULT_MAX_DIGEST_BLOCK_SIZE = 512;

    private SignatureUtils() {
        throw new AssertionError();
    }

    public static boolean verifyBase64(
            String content,
            String signature,
            PublicKey publicKey
    )
            throws InvalidKeyException, SignatureException, NoSuchAlgorithmException {
        rejectIfNull(publicKey, "publicKey");

        LoggerFactory.getLogger(SignatureUtils.class).trace("Verify \"{}\"\n with signature(base64): \"{}\"", content, signature);

        return verify(content, StandardCharsets.UTF_8, Base64.getDecoder().decode(signature), publicKey);
    }

    public static boolean verify(
            String content,
            Charset charset,
            byte[] signature,
            PublicKey publicKey
    )
            throws InvalidKeyException, SignatureException, NoSuchAlgorithmException {
        rejectIfNull(charset, "charset");
        rejectIfNull(publicKey, "publicKey");

        if (isEmpty(content)) {
            return true;
        } else {
            final var sig = Signature.getInstance(DEFAULT_SIGN_ALGORITHM);

            sig.initVerify(publicKey);

            var p = 0;
            var l = content.length();

            int pe;
            for (var lr = l - DEFAULT_MAX_DIGEST_BLOCK_SIZE; p < lr; p = pe) {
                pe = p + DEFAULT_MAX_DIGEST_BLOCK_SIZE;
                sig.update(content.substring(p, pe).getBytes(charset));
            }

            if (p < l) {
                sig.update(content.substring(p).getBytes(charset));
            }

            return sig.verify(signature);
        }
    }

    public static String signBase64(
            String content,
            PrivateKey privateKey
    )
            throws InvalidKeyException, SignatureException, NoSuchAlgorithmException {
        rejectIfNull(privateKey, "privateKey");

        final var result = Base64.getEncoder().encodeToString(sign(content, StandardCharsets.UTF_8, privateKey));

        LoggerFactory.getLogger(SignatureUtils.class).trace("Sign \"{}\"\n to signature(base64): \"{}\"", content, result);

        return result;
    }

    public static byte[] sign(
            String content,
            Charset charset,
            PrivateKey privateKey
    )
            throws InvalidKeyException, SignatureException, NoSuchAlgorithmException {
        rejectIfNull(charset, "charset");
        rejectIfNull(privateKey, "privateKey");

        if (isEmpty(content)) {
            return new byte[0];
        } else {
            final var sig = Signature.getInstance(DEFAULT_SIGN_ALGORITHM);

            sig.initSign(privateKey);

            var p = 0;
            var l = content.length();

            int pe;
            for (var lr = l - DEFAULT_MAX_DIGEST_BLOCK_SIZE; p < lr; p = pe) {
                pe = p + DEFAULT_MAX_DIGEST_BLOCK_SIZE;
                sig.update(content.substring(p, pe).getBytes(charset));
            }

            if (p < l) {
                sig.update(content.substring(p).getBytes(charset));
            }

            return sig.sign();
        }
    }
}
